Structuring and constructing modules part 2: Package-like Modules
The previous parts teach you on how to import a module (whether that module is a package or a local scripts/folders) and declare exports from a module. In this section, you’ll learn on how to write a module that emulates the modular programming paradigm in other languages such as Python. One of the strengths of {box} is that, unlike R packages, it requires less boilerplate to write a module just to make R codes reusable and make it accessible to other end-users. By composing a module, this feels like you are writing a package without requiring another boilerplates. It’s modular, maintainable, and accessible.
Get started: modules from the root folder
Recall the not_func.r script on the previous part of Chapter 3. Keep it and this time, create another scripts named convert.r and hello_world.r.
TipDid you know?
Also recall that #' @export is a borrowed syntax from Roxygen2 and is used if necessary when you declare exports of the module on specific part of the assigned R code into the namespace. There’s box::export() but the #' @export tag is more preferred than it. When #' @export is placed, it triggers the Roxygen2 functionality, where it keeps the public namespace from the private namespace.
As you can see, the composability is that simple. To declare the exports, choose either #' @export tag or through box::export(), e.g. box::export(celsius_to_fahrenheit, fahrenheit_to_celsius), box::export(hello, bye). To reuse them, simply refer their relative paths in valid bare names, not on quotes, under box::use() call.
box::use( ./module/convert, greet = ./module/hello_world)temperature =212glue::glue("{temperature} degrees Fahrenheit is equivalent to {convert$fahrenheit_to_celsius(temperature)} degrees Celsius")
212 degrees Fahrenheit is equivalent to 100 degrees Celsius
greet$hello("Amy")
Assuming you have __init__.r initialization file already but still empty. If empty and when ./module is imported on box::use(), you’ll get nothing. __init__.r must contain some exports to mark ./module as a package. Here’s an example __init__.r:
#' @exportbox::use( ./hello_world, ./not_func, ./convert, )# box::export(hello_world, not_func) # only when `#' @export` tag is not used
Then try the following:
box::use(md = ./module)temperature =212glue::glue("{temperature} degrees Fahrenheit is equivalent to {md$convert$fahrenheit_to_celsius(temperature)} degrees Celsius")
212 degrees Fahrenheit is equivalent to 100 degrees Celsius
md$hello_world$hello("Amy")
If you want functions from hello_world.r to be directly accessible, i.e. no deep $ extract, just from module itself, but hello_world is not a module under ./module anymore, then do the following:
And by the way, when you are on interactive session (say, on your R console), you can apply aliases for the script accessed as a module, not limited to functions and any objects:
Under ./module, let’s create a subfolder named statistics/, where the small set of statistical functions are contained in separate scripts. Don’t forget the __init__.r initialization file to mark whole statistics/ as a module.
mas = tsm$moving_average(AirPassengers, win =3)plot(seq_along(AirPassengers), AirPassengers, type ="l",col ="blue", lwd =2, xlab ="Time Index", ylab ="Passengers", main ="AirPassengers with Moving Averages")lines(mas, col ="red")
If you want everything under statistics/ subfolder as a module, here’s what __init__.r file should looks like:
And when you try want the every function directly accessible when statistics/ subfolder is directly imported, this is what __init__.r should looks like: